/*
 *  Copyright (c) 2021@boostmerlin<https://github.com/boostmerlin/summer-boot>
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in all
 *  copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *  SOFTWARE.
 */

package io.summer.gadget.channel.core.autoconfig;

import io.summer.basis.core.MyApplicationContext;
import io.summer.basis.excepts.AppException;
import io.summer.basis.tool.MyReflectionTool;
import io.summer.gadget.channel.SummerChannel;
import io.summer.gadget.channel.core.ChannelClientScan;
import io.summer.gadget.channel.core.TopicListener;
import io.summer.gadget.channel.core.anno.ChannelClient;
import io.summer.gadget.channel.core.anno.ChannelTopic;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.*;

/**
 * @author Moonlight
 */

@Slf4j
public class TopicListenerRegistry extends MyApplicationContext {
  private SummerChannel summerChannel;

  private final static String BASE_PACKAGE_CLS = "basePackageClasses";
  private final static String BASE_PACKAGE = "basePackages";

  private List<String> resolveComponentScans(Object source) {
    List<String> basePackages = new ArrayList<>();
    AnnotationMetadata metadata = AnnotationMetadata.introspect(source.getClass());
    Collection<AnnotationAttributes> componentScans = MyReflectionTool.attributesForRepeatable(metadata, ComponentScans.class, ComponentScan.class);
    for (AnnotationAttributes componentScan : componentScans) {
      String[] basePackagesArray = componentScan.getStringArray(BASE_PACKAGE);
      for (String pkg : basePackagesArray) {
        String[] tokenized = StringUtils.tokenizeToStringArray(this.getEnvironment().resolvePlaceholders(pkg),
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        Collections.addAll(basePackages, tokenized);
      }
      for (Class<?> clazz : componentScan.getClassArray(BASE_PACKAGE_CLS)) {
        basePackages.add(ClassUtils.getPackageName(clazz));
      }
      if (basePackages.isEmpty()) {
        basePackages.add(ClassUtils.getPackageName(metadata.getClassName()));
      }
    }
    return basePackages;
  }

  private void registerOneTopicListenerHandler(Class<?> listenerService, Object serviceIns) {
    List<Method> methods = MyReflectionTool.getClassMethodsWithAnnotation(listenerService, ChannelTopic.class);
    for (Method m : methods) {
      if (m.getParameterCount() != 1) {
        throw new AppException("TopicListener handler method param count should be 1. %s-%s"
          , m.getName(), listenerService.getName());
      }
      TopicListener<?, ?> listener = createHandlerProxyIns(listenerService, serviceIns, m);
      registerTopicListener(ChannelTopicNameHelper.getTopic(m, listenerService), listener);
    }
  }

  private void ensureChannel() {
    if (summerChannel == null) {
      summerChannel = applicationContext.getBean(SummerChannel.class);
    }
  }

  private void registerTopicListener(String topic, TopicListener<?, ?> listener) {
    log.info("auto register topic-------->");
    ensureChannel();
    summerChannel.subscribe(topic, listener);
  }

  private TopicListener<?, ?> createHandlerProxyIns(Class<?> serviceType, Object serviceIns, Method handler) {
    String pkgName = ClassUtils.getPackageName(serviceType);
    String className = ChannelTopicNameHelper.getTopicListenerName(serviceType, handler);
    try {
      Class<?> clazz = serviceType.getClassLoader().loadClass(pkgName + "." + className);
      return (TopicListener<?, ?>) clazz.getDeclaredConstructor(serviceType)
        .newInstance(serviceIns);
    } catch (Exception e) {
      throw new RuntimeException("createHandlerProxyIns failed: ", e);
    }
  }

  private List<String> getScanningPackages() {
    Map<String, Object> beans = applicationContext.getBeansWithAnnotation(ChannelClientScan.class);
    List<String> basePackages = new ArrayList<>();
    for (Object b : beans.values()) {
      ChannelClientScan a = AnnotationUtils.findAnnotation(b.getClass(), ChannelClientScan.class);
      if (a != null) {
        Collections.addAll(basePackages, a.value());
      }
    }
    return basePackages;
  }

  private void initRpcAbility(Class<?> c) {
    ChannelClient client = c.getDeclaredAnnotation(ChannelClient.class);
    if (client.rpcEnable()) {
      log.info("---------- rpc is enabled, generator proxy for: {}", c.getSimpleName());
      ChannelProxyRegistry channelProxyRegistry = applicationContext.getBean(ChannelProxyRegistry.class);
      channelProxyRegistry.addChannelProxy(c);
    }
  }

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    super.setApplicationContext(applicationContext);
    Collection<String> basePackages = getScanningPackages();
    log.info("scan all ListenerHandlerService in: {}", basePackages);
    for (String pkg : basePackages) {
      List<Class<?>> cc = MyReflectionTool.getClassWithAnnotations(pkg, ChannelClient.class);
      for (Class<?> c : cc) {
        try {
          Object handlerIns = applicationContext.getBean(c);
          registerOneTopicListenerHandler(c, handlerIns);
        } catch (BeansException ignored) {
          log.info("--------- ChannelClient do not has a instance: {}", c.getCanonicalName());
          initRpcAbility(c);
        }
      }
    }
  }
}
